﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Net.Sockets;
using System.Threading;
using System.Collections;
using System.Net;
using System.IO;

namespace Example
{
	/// <summary>
	/// Interaction logic for SalonChat.xaml
	/// </summary>
	public partial class SalonChat : Window
	{
        /*Variables de conexion*/
        private TcpListener tcpListener = null; //Socket a la espera de punto remoto 
        private TcpClient tcp = null; //Socket de comunicación para el cliente
        private Thread hiloServerCliente = null; //Hilo para el servidor 
        private byte[] dataToSend = null; //Datos a enviar 
        public ArrayList conexiones; //Colección de conexiones creada 
        string ipLocal; //IP Local
        string puertoLocal;
        public int nJugadores = 0;
        public string nuevoPuerto;
        /**/
        private Usuario user;
        private string nombreJugador;
        private ArrayList listaJugadores = new ArrayList();
        public Boolean estadoJugado;

        /*Constantes codificadas*/
        /// <summary>
        /// Mensaje cifrado para unirse al chat
        /// </summary>
        private String unirseAlChat = "/JoinPlayer";
        /// <summary>
        /// Mensaje cifrado para salirse del chat
        /// </summary>
        private String quitarChat = "/QuitPlayer";
        /// <summary>
        /// Mensaje cifrado para retar a un jugador
        /// </summary>
        private String retarJugador = "/DuelPlayer";
        /// <summary>
        /// Mensaje cifrado para aceptar el reto
        /// </summary>
        private String aceptarReto = "/AcceptDuel";
        /// <summary>
        /// Mensaje cifrado para rechazar el reto
        /// </summary>
        private String negarReto = "/DenieDuel";
        /// <summary>
        /// Mensaje cifrado para notificar que estamos jugando actualmente
        /// </summary>
        private String estoyJugando = "/AlreadyPlaying";
        /**/

        /**/
        VentanaReto vent;
        String endPointOrigenReto;
        /*Conexiones auxiliares*/
        TcpListener aux = null;

        /// <summary>
        /// Constructor del Salon Chat, donde todos los clientes se unen para charlar y jugar
        /// </summary>
        /// <param name="ip">Ip donde se realiza la conexión con el Servidor</param>
        /// <param name="puerto">Puerto para realizar la conexión con el Servidor</param>
        /// <param name="nombre">Nombre del jugador</param>

		public SalonChat(String ip, String puerto, String nombre)
		{
            /*Inicializacion*/
			this.InitializeComponent();
            this.ipLocal = ip;
            puertoLocal = puerto;
            this.nombreJugador = nombre;
            this.estadoJugado = false;//Inicialmente no estamos jugando con nadie

            /* Construimos el objeto usuario consultando en la base de datos */
            user = new Usuario(nombreJugador);

            /*Rellenamos la ventana salon con datos*/
            this.userInfoLbl.Content = user.getNombre();
            this.emailInfoLbl.Content = user.getEmail();
            this.puntosInfoLbl.Content = user.getRanking();
            if (user.getAvatar() != null)
            {
                MemoryStream byteStream = new MemoryStream(user.getAvatar());
                BitmapImage imagen = new BitmapImage();
                imagen.BeginInit();
                imagen.StreamSource = byteStream;
                imagen.EndInit();
                avatarImg.Source = imagen;
            }
                        
            conexiones = new ArrayList();

            /*Conexiones*/
            if (!conexionConServidor())
            {
                Ventana vent = new Ventana(2);
                vent.Show();
                //EmergenteVentana emergenteSalon = new EmergenteVentana();
                //emergenteSalon.infoLbl.Content = "Error de conexion";
            }//Realizamos la conexion con el servidor
		}

        /// <summary>
        /// Método para obtener la Ip del cliente
        /// </summary>
        /// <returns>Devuelve una cadena con la IP a conectarse</returns>
        private string LocalIPAddress()
        {
            IPHostEntry host;
            string localIP = "";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    //Mediante este if descartamos las Ips erroneas
                    /*if (ip.ToString().Substring(0, 3) != "192")
                    {
                        localIP = ip.ToString();
                    }*/
                    //Descomentar en casa y comentar el If, ya que puede dar problemas
                    localIP = ip.ToString();
                }
            }
            return localIP;
        }


        /// <summary>
        /// Clase que define las constantes existentes
        /// </summary>
        public class Constants
        {
            public const int noOfBytesToReadForSuperPacket = 4;
            public const int maxNoOfBytes = 784;
        }

        /// <summary>
        /// Método que manda un mensaje a una conexión abierta actualmente
        /// </summary>
        /// <param name="tc">Conexión a la que mandar el mensaje - en este caso será el servidor</param>
        /// <param name="msg">Mensaje a mandar</param>
        private void enviarMensaje(TcpClient tc, string msg)
        {
            //Prepara mensaje para enviar como array de bytes
            char[] charArray = msg.ToCharArray(0,
            msg.Length);
            dataToSend = new byte[msg.Length];
            for (int charCount = 0; charCount < msg.Length; charCount++)
            {
                dataToSend[charCount] = (byte)charArray[charCount];
            }
            //Usamos el socket para enviar el mensaje
            tc.GetStream().Write(dataToSend, 0, dataToSend.Length);
        }


        /// <summary>
        /// Delegado para recibir mensajes y modificar la interfaz
        /// </summary>
        /// <param name="msg">Mensaje recibido por un socket</param>
        private delegate void RecibirMensajeDel(string msg);

        public void delegarRecibirMensaje(string msg)
        {
            RecibirMensajeDel aux = new RecibirMensajeDel(this.escribirMensaje);
            Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, aux, msg);
        }

        /// <summary>
        /// Método que parsea los mensajes que recibe el Cliente y realiza las acciones pertinentes
        /// </summary>
        /// <param name="msg">Mensaje recibido representado ya en String</param>
        private void escribirMensaje(string msg)
        { //Escribo en mi interfaz a través de la invocación del delegado (Cliente)

            /*Primero hay que parsear el mensaje (Codificacion o mensaje normal)*/
            
            //Un jugador se une al chat, entonces lo añadimos a la lista de jugadores
            if (msg.Contains(unirseAlChat))
            {
                //Actualizamos la tabla
                usersList.Items.Clear();

                char[] delimitador = new char[] { '*' };
                foreach (string substr in msg.Split(delimitador))
                {
                    //Si no contiene la barra y no contiene una cadena vacia
                    if ( !substr.Contains("/") && substr!="" )
                    {
                        //Lo añado a nuestra vista si no esta
                        if (!usersList.Items.Contains(substr))
                        {
                            listaJugadores.Add(substr);
                            //Refresco la vista
                            usersList.Items.Add(substr);
                        }

                    }
                }
            }
            //Me mandan una invitacion de reto, lanzamos la ventana 
            else if (msg.Contains(retarJugador))
            {
                String[] subcadena = null;
                //Si el usuario no esta jugando entonces le llega la invitacion
                if (!estadoJugado)
                {
                    //Tenemos que parsear el mensaje primero
                    char[] delimitador = new char[] { ' ' };
                    foreach (string substr in msg.Split(delimitador))
                    {
                        //Si no contiene la barra y no contiene una cadena vacia
                        if (!substr.Contains("/") && substr != "")
                        {
                            char[] delimiterChars = { '-' };
                            subcadena = substr.Split(delimiterChars);
                            //endPointOrigenReto = substr;//Obtendo el endPoint de quien me reta
                            endPointOrigenReto = subcadena[0];
                        }
                    }

                    /* Lanzamos la ventana de reto */
                    vent = new VentanaReto();
                    vent.Show();
                    vent.retoLbl.Content = "El jugador " + subcadena[1] + " te ha retado:";
                    vent.siRetoBtn.Click += new RoutedEventHandler(this.siRetoBtn_Click);
                    vent.noRetoBtn.Click += new RoutedEventHandler(this.noRetoBtn_Click);
                    
                    
                }
                //Si el usuario esta jugando
                else
                {
                    //Mandar un mensaje al otro de error por ejemplo
                    this.enviarMensajeCifrado(estoyJugando + " " + nombreJugador + " " + endPointOrigenReto);
                }
                
            }
            //Nos aceptan el reto que lanzamos
            else if (msg.Contains(aceptarReto))
            {
                //Creamos la partida - El cliente que crea la partida es quien la gestiona
                this.estadoJugado = true;
                crearPartida();
            }
            //Nos niegan el reto
            else if (msg.Contains(negarReto))
            {
                Ventana vent = new Ventana(5);
                vent.Show();
            }
            //El otro cliente esta jugando
            else if(msg.Contains(estoyJugando)){
                Ventana vent = new Ventana(6);
                vent.Show();
            }

            //Mensaje de texto normal
            else
            {
                Paragraph p = new Paragraph();
                p.Inlines.Add(new Run(msg));
                fdocAux.Blocks.Add(p);
            }

        }

        /// <summary>
        /// Método que crea una partida contra otro jugador (Esto se produce cuando Cliente reta a otro Cliente)
        /// </summary>
        private void crearPartida()
        {
            Conexion aux = (Conexion)this.conexiones[0];//Recupero  mi conexion
            Partida partidaNueva = new Partida(this, true, aux.getSocket().Client.RemoteEndPoint.ToString(), nombreJugador);//Actuamos como servidor
            this.Hide();
            partidaNueva.usuarioLbl.Content = nombreJugador;//Ponemos el nombre del jugador
            partidaNueva.Show();

        }

        /// <summary>
        /// Evento que se produce cuando se cierra la ventana, se matán todos los procesos y se cierra la conexión
        /// </summary>
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Conexion auxcon = (Conexion)this.conexiones[0];
            this.enviarMensajeCifrado(quitarChat+" "+nombreJugador);//Al servidor del decimos que nos vamos
            auxcon.stopThread();//Paramos el hilo parte cliente
            Environment.Exit(0);//Cerramos la ventana

        }

        /// <summary>
        /// Método que se encarga de realizar la conexión con el servidor a través de un socket
        /// </summary>
        /// <returns>Devuelve True si la conexión fue buena, False en caso contrario</returns>
        private Boolean conexionConServidor()
        {
             
            //Intentamos la conexion
            try
            {
                this.Title = "Salon Quien Es Quien";
                IPAddress address = IPAddress.Parse(this.ipLocal);
                //Conectar al socket destino en espera
                tcp = new TcpClient(new IPEndPoint(IPAddress.Parse(ipLocal), int.Parse(puertoLocal)+1));
                LingerOption lingerOption = new LingerOption(false, 1);
                tcp.LingerState = lingerOption;
                // Concetarnos al puerto e ip remotos
                tcp.Connect(IPAddress.Parse(ipLocal), int.Parse(puertoLocal));
                nuevoPuerto = recibirMensaje();
                tcp.Close();
                tcp = new TcpClient(new IPEndPoint(IPAddress.Parse(ipLocal), int.Parse(nuevoPuerto)+1));
                tcp.Connect(IPAddress.Parse(ipLocal), int.Parse(nuevoPuerto));
                this.conexiones.Add(new Conexion(this, tcp));
                this.nJugadores++; //En el cliente solo habrá uno
                this.delegarRecibirMensaje("Usuario: " + this.userInfoLbl.Content + " conectado");
                //this.enviarMensajeCifrado("/" + nombreJugador);//Le damos al servidor nuestro nombre

                //Mandamos un mensaje al servidor dandole nuestro nombre y direccion
                Conexion aux = (Conexion)this.conexiones[0];//Recupero  mi conexion
 
                this.enviarMensajeCifrado(unirseAlChat+" "+nombreJugador+" "+aux.getSocket().Client.RemoteEndPoint.ToString());

                return true;
            }
            //Si la conexion falla por cualquier motivo retornamos un false
            catch
            {
                return false;
            }
            
        }
        /// <summary>
        /// Deprecated
        /// </summary>
        private ArrayList recibirUsuarios()
        {
            ArrayList listaUsuarios = new ArrayList();
            return listaUsuarios;
        }

        /// <summary>
        /// Método que recibe los bytes por un socket y los convierte en un String
        /// </summary>
        /// <returns>El string resultante del mensaje recibido</returns>
        private string recibirMensaje()
        {
            int readBytes = 0;
            StringBuilder msg = new StringBuilder(Constants.maxNoOfBytes);
            byte[] byteNom = new byte[Constants.maxNoOfBytes];
            byte count1 = 0x01;
            for (int i = 0; i < byteNom.Length; i++)
                byteNom[i] = count1++;
            while (readBytes == 0)
            {
                readBytes = tcp.GetStream().Read(byteNom, 0, byteNom.Length);
                for (int i = 0; i < readBytes; i++)
                    msg.Append((char)byteNom[i]);
                Thread.Sleep(200);
            }
            string mensaje = msg.ToString();
            if (mensaje == null) mensaje = recibirMensaje();
            return mensaje;
        }

        /// <summary>
        /// Evento que se produce al pulsar enviar, manda un mensaje de texto a los demas usuarios y controla ciertos aspectos como que no sea vacio
        /// </summary>
        private void enviarChatBtn_Click(object sender, RoutedEventArgs e)
        {
            if (this.inputBox.Text.Length != 0)
            { //Escribir mensaje en la ventana local 
                //Componemos el mensaje
                String textoMandar = nombreJugador + "> " + this.inputBox.Text;

                //El cliente solo pinta cuando recibe

                for (int i = 0; i < nJugadores; i++)//Para cada una de las conexiones le mandamos un mensaje
                {//Recordar que este array lo tiene el servidor, el cliente solo tiene una conexion
                    Conexion auxcon = (Conexion)conexiones[i];
                    auxcon.enviarMensaje(textoMandar);
                }
                /*Inicializamos las variables*/
                this.inputBox.Text = "";
            }
        }

        /// <summary>
        /// Método que envia un mensaje cifrado al servidor, se utiliza para el uso de determinadas acciones
        /// </summary>
        /// <param name="msg">Mensaje cifrado a enviar al servidor</param>
        private void enviarMensajeCifrado(String msg)
        {
            for (int i = 0; i < nJugadores; i++)//Para cada una de las conexiones le mandamos un mensaje
            {//Recordar que este array lo tiene el servidor, el cliente solo tiene una conexion
                Conexion auxcon = (Conexion)conexiones[i];
                auxcon.enviarMensaje(msg);//Mandamos el mensaje cifrado
            }
        }

        /// <summary>
        /// Deprecated
        /// </summary>
        private void inputBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            
        }


        /// <summary>
        /// Método que se encarga de realizar un reto al jugador seleccionado de la lista
        /// </summary>
        private void retartBtn_Click(object sender, RoutedEventArgs e)
        {
  
            //Si tenemos algún jugador seleccionado y ademas ese jugador no somos nosotros
            if(usersList.SelectedItem != null && usersList.SelectedItem.ToString() != nombreJugador){
                Conexion aux = (Conexion)this.conexiones[0];//Recupero  mi conexion
                //Orden + NombreJugadorDestino + DireccionEndPointOrigen
                this.enviarMensajeCifrado(retarJugador + " " + usersList.SelectedItem.ToString() + " "
                    + aux.getSocket().Client.RemoteEndPoint.ToString() + "-" + user.getNombre());
            }

        }

        /// <summary>
        /// Método que se encarga de aceptar un reto que nos envian
        /// </summary>
        private void siRetoBtn_Click(object sender, RoutedEventArgs e)
        {
            //Orden + NombreJugadorOrigen + DireccionEndPointOrigen
            this.estadoJugado = true;
            this.enviarMensajeCifrado(aceptarReto + " " + nombreJugador + " " + endPointOrigenReto);
            this.Hide();//Ocultamos la pantalla del salon
            this.vent.Close();//Cerramos la ventana
            Partida partidaNueva = new Partida(this, false, endPointOrigenReto, nombreJugador);//Actuamos como clientes


            /*Podemos intentar lanzar un nuevo hilo que contenga la partida de los dos jugadores*/
            partidaNueva.Show();
        }

        /// <summary>
        /// Método que se encarga de rechazar el reto que nos envian
        /// </summary>
        private void noRetoBtn_Click(object sender, RoutedEventArgs e)
        {
            this.vent.Close();//Cerramos la ventana
            //Mandamos un mensaje de que no queremos jugar al otro usuario
            this.enviarMensajeCifrado(negarReto + " " + nombreJugador + " " + endPointOrigenReto);

        }
	}
}